Passed
Push — master ( 6da7f5...ac5be4 )
by Kolja
01:13
created

jgfGraph.js ➔ label   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 2
b 0
f 1
nc 1
dl 0
loc 3
rs 10
nop 0
1
const check = require('check-types');
2
const _ = require('lodash');
3
const { cloneObject } = require('./common');
4
const { JGFEdge } = require('./jgfEdge');
5
const { Guard } = require('./guard');
6
7
/**
8
 * A single JGF graph instance, always contained in a parent JGFContainer
9
 */
10
class JGFGraph {
11
12
    /**
13
     * Constructor
14
     * @param {*} type graph classification
15
     * @param {*} label a text display for the graph
16
     * @param {*} directed true for a directed graph, false for an undirected graph
17
     * @param {*} metadata about the graph
18
     */
19
    constructor(type = '', label = '', directed = true, metadata = null) {
20
        this._nodes = [];
21
        this._edges = [];
22
23
        this._type = type;
24
        this._label = label;
25
        this._directed = directed;
26
        this._metadata = metadata;
27
    }
28
29
30
    /**
31
     * Loads the graph from a JGF JSON object
32
     * @param {*} graphJson JGF JSON object
33
     */
34
    loadFromJSON(graphJson) {
35
        this._type = graphJson.type;
36
        this._label = graphJson.label;
37
        // todo: this makes the graph always directed (even if false is passed), I doubt that this was the intention here
38
        this._directed = graphJson.directed || true;
39
        this._metadata = graphJson.metadata;
40
41
        this._nodes = [];
42
        this._edges = [];
43
        this.addNodes(graphJson.nodes);
44
        this.addEdges(graphJson.edges);
45
    }
46
47
    /**
48
     * @param {JGFNode} node Node to be found.
49
     * @private
50
     */
51
    _findNode(node) {
52
        return this._findNodeById(node.id);
53
    }
54
55
    /**
56
     * @param {string} nodeId Node to be found.
57
     * @private
58
     */
59
    _findNodeById(nodeId) {
60
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
61
        if (!foundNode) {
62
            throw new Error(`A node does not exist with id = ${nodeId}`);
63
        }
64
65
        return foundNode;
66
    }
67
68
    /**
69
     * @param {JGFNode} node Node to be found.
70
     * @private
71
     */
72
    _nodeExists(node) {
73
        return this._nodeExistsById(node.id);
74
    }
75
76
    /**
77
     * @param {string} nodeId Node to be found.
78
     * @private
79
     */
80
    _nodeExistsById(nodeId) {
81
        let foundNode = _.find(this._nodes, (existingNode) => existingNode.id === nodeId);
82
83
        return Boolean(foundNode);
84
    }
85
86
    /**
87
     * Set the graph meta data
88
     */
89
    set metadata(value) {
90
        Guard.assertValidMetadataOrNull(value);
91
        this._metadata = value;
92
    }
93
94
    /**
95
     * Returns the graph meta data
96
     */
97
    get metadata() {
98
        return this._metadata;
99
    }
100
101
    /**
102
     * Returns all nodes
103
     */
104
    get nodes() {
105
        return this._nodes;
106
    }
107
108
    /**
109
     * Returns all edges
110
     */
111
    get edges() {
112
        return this._edges;
113
    }
114
115
    /**
116
     * Returns the graph as JGF Json
117
     */
118
    get json() {
119
        let json = {
120
            type: this._type,
121
            label: this._label,
122
            directed: this._directed,
123
            nodes: this._nodes,
124
            edges: this.edges,
125
        };
126
127
        let metadata = this._getJsonMetadata();
128
        if (metadata) {
129
            json.metadata = metadata;
130
        }
131
132
        return cloneObject(json);
133
    }
134
135
    _getJsonMetadata() {
136
        let metadata = null;
137
        if (check.assigned(this._metadata)) {
138
            metadata = this._metadata;
139
        }
140
141
        return metadata;
142
    }
143
144
    /**
145
     * Adds a new node
146
     * @param {JGFNode} node Node to be added.
147
     */
148
    addNode(node) {
149
        if (this._nodeExists(node)) {
150
            throw new Error(`A node already exists with id = ${node.id}`);
151
        }
152
153
        this._nodes.push(node);
154
    }
155
156
157
    /**
158
     * Adds multiple nodes
159
     * @param {JGFNode[]} nodes A collection of JGF node objects.
160
     */
161
    addNodes(nodes) {
162
        for (let node of nodes) {
163
            this.addNode(node);
164
        }
165
    }
166
167
    /**
168
     * Removes an existing graph node
169
     * @param {JGFNode} node Node to be removed.
170
     */
171
    removeNode(node) {
172
        if (!this._nodeExists(node)) {
173
            throw new Error(`A node does not exist with id = ${node.id}`);
174
        }
175
176
        _.remove(this._nodes, (existingNode) => existingNode.id === node.id);
177
    }
178
179
    /**
180
     * Get a node by a node id.
181
     * @param {string} nodeId Unique node id
182
     */
183
    getNodeById(nodeId) {
184
        return this._findNodeById(nodeId);
185
    }
186
187
    /**
188
     * Adds an edge between a source node and a target node.
189
     * @param {JGFEdge} edge Source node id
190
     */
191
    addEdge(edge) {
192
        this._guardAgainstNonExistentNodes(edge.source, edge.target);
193
        this._edges.push(edge);
194
    }
195
196
    _guardAgainstNonExistentNodes(source, target) {
197
        if (!this._nodeExistsById(source)) {
198
            throw new Error(`addEdge failed: source node isn't found in nodes. source = ${source}`);
199
        }
200
201
        if (!this._nodeExistsById(target)) {
202
            throw new Error(`addEdge failed: target node isn't found in nodes. target = ${target}`);
203
        }
204
    }
205
206
    /**
207
     * Adds multiple edges
208
     * @param {JGFEdge[]} edges A collection of JGF edge objects.
209
     */
210
    addEdges(edges) {
211
        for (let edge of edges) {
212
            this.addEdge(edge);
213
        }
214
    }
215
216
    /**
217
     * Removes existing graph edge.
218
     * @param {JGFEdge} edge Edge to be removed.
219
     */
220
    removeEdge(edge) {
221
        _.remove(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, true));
222
    }
223
224
    /**
225
     * Get edges between source node and target node, with an optional edge relation.
226
     * @param {string} source Source node ID.
227
     * @param {string} target Target node ID.
228
     * @param {string,null} relation
229
     */
230
    getEdgesByNodes(source, target, relation = null) {
231
        this._guardAgainstNonExistentNodes(source, target);
232
233
        let edge = new JGFEdge(source, target, relation);
234
235
        return _.filter(this._edges, (existingEdge) => existingEdge.isEqualTo(edge, check.assigned(relation)));
236
    }
237
238
    get graphDimensions() {
239
        let dimensions = {
240
            nodes: 0,
241
            edges: 0,
242
        };
243
244
        dimensions.nodes = Object.keys(this._nodes).length;
245
        dimensions.edges = this._edges.length;
246
247
        return dimensions;
248
    }
249
}
250
251
module.exports = {
252
    JGFGraph,
253
};